﻿using System;

namespace FinMkts.Messaging.Pgm
{
    /// <summary>
    /// A number of bits of storage, in various formats, e.g. Kb, KB, MB, GB
    /// </summary>
    public struct BinarySize : IEquatable<BinarySize>
    {
        private readonly ulong _bytes;
        
        public static readonly BinarySize Zero = new BinarySize();

        public BinarySize(ulong bytes)
        {
            _bytes = bytes;
        }

        public ulong KiloBits
        {
            get { return _bytes / 128; }    // 1024/8 == 128
        }

        public double MegaBits
        {
            get { return KiloBits / 1024d; }
        }

        public double GigaBits
        {
            get { return MegaBits / 1024d; }
        }

        public ulong Bytes
        {
            get { return _bytes; }
        }

        public double KiloBytes
        {
            get { return Bytes / 1024d; }
        }

        public double MegaBytes
        {
            get { return KiloBytes / 1024d; }
        }

        public double GigaBytes
        {
            get { return MegaBytes / 1024d; }
        }

        public static BinarySize FromBytes(ulong value)
        {
            return new BinarySize(value);
        }

        public static BinarySize FromKiloBits(ulong value)
        {
            return new BinarySize(value * 128);
        }

        public static BinarySize FromMegaBits(double value)
        {
            return FromKiloBits((ulong) (value*1024));
        }

        public static BinarySize FromGigaBits(double value)
        {
            return FromMegaBits(value*1024);
        }

        public static BinarySize FromKiloBytes(double value)
        {
            return FromBytes((uint) (value*1024));
        }  

        public static BinarySize FromMegaBytes(double value)
        {
            return FromKiloBytes(value*1024);
        }  

        public static BinarySize FromGigaBytes(double value)
        {
            return FromMegaBytes(value*1024);
        }

        public override string ToString()
        {
            return ToString(BitsBytes.Bytes);
        }
        
        public string ToString(BitsBytes bitsBytes)
        {
            switch (bitsBytes)
            {
                case BitsBytes.Bits:
                    return ToBitsString();
                case BitsBytes.Bytes:
                    return ToBytesString();
                default:
                    throw new ArgumentOutOfRangeException("bitsBytes", bitsBytes, "Unexpected value");
            }
        }

        string ToBitsString()
        {
            if (KiloBits < 1024) return string.Format("{0:N1} Kb", KiloBits);
            if (MegaBits < 1024) return string.Format("{0:N1} Mb", MegaBits);
            return string.Format("{0:N1} Gb", GigaBits);
        }

        string ToBytesString()
        {
            if (_bytes < 1024) return string.Format("{0} Bytes", Bytes);
            if (KiloBytes < 1024) return string.Format("{0:N1} KB", KiloBytes);
            if (MegaBytes < 1024) return string.Format("{0:N1} MB", MegaBytes);
            return string.Format("{0:N1} GB", GigaBytes);
        }

        public bool Equals(BinarySize other)
        {
            return other._bytes == _bytes;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof (BinarySize)) return false;
            return Equals((BinarySize) obj);
        }

        public override int GetHashCode()
        {
            return _bytes.GetHashCode();
        }

        public static bool operator ==(BinarySize left, BinarySize right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(BinarySize left, BinarySize right)
        {
            return !left.Equals(right);
        }

        public static BinarySize operator -(BinarySize left, BinarySize right)
        {
            return new BinarySize(left._bytes - right._bytes);
        }

        public static BinarySize operator +(BinarySize left, BinarySize right)
        {
            return new BinarySize(left._bytes + right._bytes);
        }
    }

    public enum BitsBytes
    {
        Bits,
        Bytes,
    }
}